package com.msb.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.msb.common.utils.PageUtils;
import com.msb.common.utils.Query;
import com.msb.mall.product.dao.CategoryBrandRelationDao;
import com.msb.mall.product.entity.CategoryBrandRelationEntity;
import com.msb.mall.product.vo.Catalog2Vo;
import com.mysql.cj.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.Tag;
import org.apache.skywalking.apm.toolkit.trace.Tags;
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.msb.mall.product.dao.CategoryDao;
import com.msb.mall.product.entity.CategoryEntity;
import com.msb.mall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private CategoryBrandRelationDao categoryBrandRelationDao;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listTree() {

        // 查询所有的商品分类
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);

        // 将商品分类拆解成树形结构返回
        List<CategoryEntity> cateGoryList = categoryEntities.stream().filter(categoryEntitiy ->
                //查询所有的大类
                categoryEntitiy.getParentCid() == 0
        ).map(categoryEntity -> {
                    // 查询大类下面所有的小类  递归
                    categoryEntity.setChildren(getCateGoryEntityChirds(categoryEntity, categoryEntities));
                    return categoryEntity;
                }
        ).sorted((entity1, entity2) -> {
                    return (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort());
                }
        ).collect(Collectors.toList());

        return cateGoryList;
    }

    /**
     * 逻辑批量删除
     * @param asList
     */
    @Override
    public void removeCateGoryByIds(List<Long> asList) {
        baseMapper.deleteBatchIds(asList);
    }


    /**
     * 根据所属分类id找到对应的所有上级分类
     * @param catelogId
     * @return
     */
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> list = new ArrayList<>();

        List<Long> pathList = findParentId(catelogId, list);

        Collections.reverse(pathList);
        return pathList.toArray(new Long[pathList.size()]);
    }

    /**
     * 修改处理关联关系表
     * 并删除缓存中的数据，@Caching组合注解，，@CacheEvict删除操作
     * @param category
     */
    @Caching(evict = {
            @CacheEvict(value = "catagory",key = "'getLeve1Catagory'"),
            @CacheEvict(value = "catagory",key = "'getCatalog2Json'")
    })
    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if (!StringUtils.isNullOrEmpty(category.getName())){
            CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
            categoryBrandRelationEntity.setCatelogId(category.getCatId());
            categoryBrandRelationEntity.setCatelogName(category.getName());
            categoryBrandRelationDao.update(categoryBrandRelationEntity,new UpdateWrapper<CategoryBrandRelationEntity>().eq("catelog_id",category.getCatId()));
        }
    }

    /**
     * 查询一层分类，父id=0的数据
     *  在注解中我们可以定义缓存的名称，起到一个分区的作用，一般按照业务来区分
     *  @Cacheable({"catagory","product"}) 代表当前的方法的返回结果需要缓存，
     *                                      调用该方法的时候，如果缓存中有数据，那么该方法就不会执行，
     *                                      如果缓存中没有数据，那么就执行该方法，并缓存结果。
     *  缓存处理
     *      1.存储在Redis中的缓存数据的key是默认生成的： 缓存名称::SimpleKey[]
     *      2.默认缓存的数据的过期时间是-1。（即永久有效）
     *      3.缓存的数据，默认使用的是jdk的序列化机制。看不懂
     *  改进：
     *      1.生成的缓存数据我们需要自定义的key  key = "'leve1Catagory'"（字符串）或者key = "#root.method.name" （SPEL表达式）
     *      2.指定缓存数据的存活时间   配置文件中： spring.cache.redis.time-to-live=60000
     *      3.把缓存的数据保存为JSON格式
     *
     * @return
     */
    @Cacheable(value = {"catagory"},key = "#root.method.name",sync = true)
    @Override
    public List<CategoryEntity> getLeve1Catagory() {
        List<CategoryEntity> oneLevel = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return oneLevel;
    }

    /**
     * 根据父编号获取对应的分类信息
     *  业务查询优化，先查询所有的信息，再从list中取需要的信息
     */
    public List<CategoryEntity> getCategoryByParentCid(List<CategoryEntity> list,Long parentCid){

        List<CategoryEntity> catelist = list.stream().filter(item -> item.getParentCid() == parentCid).collect(Collectors.toList());
        return catelist;

    }

    /**
     * 获取二级三级分类并进行缓存
     * @return
     */
    @Trace
    @Cacheable(value = "catagory",key = "#root.methodName",sync = true)
    @Tags({
            @Tag(key = "getCatalog2Json",value = "returnedObj"),
            @Tag(key = "param" , value = "arg[0]")
    })
    @Override
    public Map<String, List<Catalog2Vo>> getCatalog2Json(){
        // 获取所有的分类信息
        List<CategoryEntity> list = this.list();
        // 一级分类
        List<CategoryEntity> oneLevelList = this.getCategoryByParentCid(list,0L);

        // 使用map收集数据key一级分类编号，value一级分类对应的二级分类集合
        Map<String, List<Catalog2Vo>> map = oneLevelList.stream().collect(Collectors.toMap(
                item -> item.getCatId().toString(), l1 -> {
                    // 一级分类对应的二级分类
                    List<CategoryEntity> l2List = this.getCategoryByParentCid(list, l1.getCatId());
                    List<Catalog2Vo> catalog2VoList = null;
                    // 二级分类对应的三级分类
                    if (null != l2List){
                        catalog2VoList = l2List.stream().map(
                                l2 -> {
                                    List<CategoryEntity> l3List = this.getCategoryByParentCid(list, l2.getCatId());
                                    List<Catalog2Vo.Catalog3Vo> catalog3VoList = null;
                                    if (null != l3List){
                                        // 封装三级分类信息
                                        catalog3VoList = l3List.stream().map(item -> {
                                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo();
                                            catalog3Vo.setCatalog2Id(l2.getCatId().toString());
                                            catalog3Vo.setId(item.getCatId().toString());
                                            catalog3Vo.setName(item.getName());
                                            return catalog3Vo;
                                        }).collect(Collectors.toList());
                                    }
                                    // 封装二级分类信息
                                    Catalog2Vo catalog2Vo = new Catalog2Vo();
                                    catalog2Vo.setCatalog1Id(l1.getCatId().toString());
                                    catalog2Vo.setCatalog3List(catalog3VoList);
                                    catalog2Vo.setId(l2.getCatId().toString());
                                    catalog2Vo.setName(l2.getName());
                                    return catalog2Vo;
                                }
                        ).collect(Collectors.toList());
                    }
                    return catalog2VoList;
                }
        ));
        return map;
    }

    /**
     * 从redis缓存中获取
     * 获取二级三级分类
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalog2JsonRedis(){
        String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.isNullOrEmpty(catalogJSON)){
            Map<String, List<Catalog2Vo>> catalog2JsonForDb = null;
            synchronized (this){
                log.info("--------------------------------->>缓存没有命中");
                catalog2JsonForDb = getCatalog2JsonDBWithRedisson();
            }
            return catalog2JsonForDb;
        }
        Map<String, List<Catalog2Vo>> map = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2Vo>>>(){});
        log.info("------------------------------>>缓存命中了");
        return map;
    }

    /**
     * 查询二三级分类信息
     * redisson实现分布式锁
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalog2JsonDBWithRedisson() {
        String key = "catalogJSON";
        RLock lock = redissonClient.getLock("catalog2JSON-lock");
        Map<String, List<Catalog2Vo>> catalogJsonDb = null;
        try {
            // 加锁
            lock.lock();
            catalogJsonDb = getCatalogJsonDb(key);
        }finally {
            lock.unlock();
        }
        return catalogJsonDb;

    }

    /**
     * redis实现分布式锁
     * 复杂，考虑因素太多，容易出错，
     * 一般使用redisson或zookeeper等工具实现分布式锁
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalog2JsonDBWithRedisLock() {
        String key = "catalogJSON";
        String uuid = UUID.randomUUID().toString();
        // 如果不存在就添加
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        if (lock){
            log.info("------------------->>加锁成功");
            // 加锁成功   查询数据库
            Map<String, List<Catalog2Vo>> catalogJsonDb = null;
            try {
                catalogJsonDb = getCatalogJsonDb(key);
            }finally {
                String srcipts = "if redis.call('get',KEYS[1]) == ARGV[1]  then return redis.call('del',KEYS[1]) else  return 0 end ";
                // 通过Redis的lua脚本实现 查询和删除操作的原子性
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(srcipts,Long.class)
                        ,Arrays.asList("lock"),uuid);
            }
            return catalogJsonDb;
        }else {
            log.info("-------------------->>加锁失败");
            // 加锁失败，
            // 休眠/重新获取
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return getCatalog2JsonDBWithRedisLock();
        }

    }

    /**
     * 从数据库查询二三级分类
     * @param key
     * @return
     */
    private Map<String, List<Catalog2Vo>> getCatalogJsonDb(String key) {
        String catalogJSON = stringRedisTemplate.opsForValue().get(key);
        if (!StringUtils.isNullOrEmpty(catalogJSON)){
            // 说明缓存命中
            Map<String, List<Catalog2Vo>> map = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2Vo>>>() {
            });
            return map;
        }
        log.info("-------------------------------->>>>查询数据库");
        // 获取所有的分类信息
        List<CategoryEntity> list = this.list();
        // 一级分类
        List<CategoryEntity> oneLevelList = this.getCategoryByParentCid(list,0L);

        // 使用map收集数据key一级分类编号，value一级分类对应的二级分类集合
        Map<String, List<Catalog2Vo>> map = oneLevelList.stream().collect(Collectors.toMap(
                item -> item.getCatId().toString(), l1 -> {
                    // 一级分类对应的二级分类
//                    List<CategoryEntity> l2List = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", l1.getCatId()));
                    List<CategoryEntity> l2List = this.getCategoryByParentCid(list, l1.getCatId());
                    List<Catalog2Vo> catalog2VoList = null;
                    // 二级分类对应的三级分类
                    if (null != l2List){
                        catalog2VoList = l2List.stream().map(
                                l2 -> {
//                                    List<CategoryEntity> l3List = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
                                    List<CategoryEntity> l3List = this.getCategoryByParentCid(list, l2.getCatId());
                                    List<Catalog2Vo.Catalog3Vo> catalog3VoList = null;
                                    if (null != l3List){
                                        // 封装三级分类信息
                                        catalog3VoList = l3List.stream().map(item -> {
                                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo();
                                            catalog3Vo.setCatalog2Id(l2.getCatId().toString());
                                            catalog3Vo.setId(item.getCatId().toString());
                                            catalog3Vo.setName(item.getName());
                                            return catalog3Vo;
                                        }).collect(Collectors.toList());
                                    }
                                    // 封装二级分类信息
                                    Catalog2Vo catalog2Vo = new Catalog2Vo();
                                    catalog2Vo.setCatalog1Id(l1.getCatId().toString());
                                    catalog2Vo.setCatalog3List(catalog3VoList);
                                    catalog2Vo.setId(l2.getCatId().toString());
                                    catalog2Vo.setName(l2.getName());
                                    return catalog2Vo;
                                }
                        ).collect(Collectors.toList());
                    }
                    return catalog2VoList;
                }
        ));
        if (null == map){
            // 防止缓存穿透
            stringRedisTemplate.opsForValue().set(key,"1",5,TimeUnit.SECONDS);
        }else {
            // 设置随机过期时间，防止缓存雪崩
            String s = JSON.toJSONString(map);
            stringRedisTemplate.opsForValue().set(key,s,new Random().nextInt(10),TimeUnit.MINUTES);
        }
        return map;
    }

    /**
     * 从数据库查询
     * 获取二级三级分类
     * 实现了同步锁，分布式项目还需要分布式锁
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalog2JsonForDb() {
        synchronized (this){

            String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
            if (!StringUtils.isNullOrEmpty(catalogJSON)){
                // 说明缓存命中
                Map<String, List<Catalog2Vo>> map = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2Vo>>>() {
                });
                return map;
            }
            log.info("-------------------------------->>>>查询数据库");
            // 获取所有的分类信息
            List<CategoryEntity> list = this.list();
            // 一级分类
            List<CategoryEntity> oneLevelList = this.getCategoryByParentCid(list,0L);

            // 使用map收集数据key一级分类编号，value一级分类对应的二级分类集合
            Map<String, List<Catalog2Vo>> map = oneLevelList.stream().collect(Collectors.toMap(
                    item -> item.getCatId().toString(), l1 -> {
                        // 一级分类对应的二级分类
//                    List<CategoryEntity> l2List = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", l1.getCatId()));
                        List<CategoryEntity> l2List = this.getCategoryByParentCid(list, l1.getCatId());
                        List<Catalog2Vo> catalog2VoList = null;
                        // 二级分类对应的三级分类
                        if (null != l2List){
                            catalog2VoList = l2List.stream().map(
                                    l2 -> {
//                                    List<CategoryEntity> l3List = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
                                        List<CategoryEntity> l3List = this.getCategoryByParentCid(list, l2.getCatId());
                                        List<Catalog2Vo.Catalog3Vo> catalog3VoList = null;
                                        if (null != l3List){
                                            // 封装三级分类信息
                                            catalog3VoList = l3List.stream().map(item -> {
                                                Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo();
                                                catalog3Vo.setCatalog2Id(l2.getCatId().toString());
                                                catalog3Vo.setId(item.getCatId().toString());
                                                catalog3Vo.setName(item.getName());
                                                return catalog3Vo;
                                            }).collect(Collectors.toList());
                                        }
                                        // 封装二级分类信息
                                        Catalog2Vo catalog2Vo = new Catalog2Vo();
                                        catalog2Vo.setCatalog1Id(l1.getCatId().toString());
                                        catalog2Vo.setCatalog3List(catalog3VoList);
                                        catalog2Vo.setId(l2.getCatId().toString());
                                        catalog2Vo.setName(l2.getName());
                                        return catalog2Vo;
                                    }
                            ).collect(Collectors.toList());
                        }
                        return catalog2VoList;
                    }
            ));
            if (null == map){
                // 防止缓存穿透
                stringRedisTemplate.opsForValue().set("catalogJSON","1",5,TimeUnit.SECONDS);
            }else {
                // 设置随机过期时间，防止缓存雪崩
                String s = JSON.toJSONString(map);
                stringRedisTemplate.opsForValue().set("catalogJSON",s,new Random().nextInt(10),TimeUnit.MINUTES);
            }
            return map;
        }
    }

    private List<Long> findParentId(Long catelogId, List<Long> list) {
        
        list.add(catelogId);

        CategoryEntity categoryEntity = baseMapper.selectById(catelogId);
        if (categoryEntity.getParentCid() != 0){
            Long parentCid = categoryEntity.getParentCid();
            findParentId(parentCid,list);
        }

        return list;

    }


    /**
     * 查询大类下面的小类  递归
     * @param categoryEntity 大类
     * @param categoryEntities 所有类别集合
     * @return
     */
    private List<CategoryEntity> getCateGoryEntityChirds(CategoryEntity categoryEntity, List<CategoryEntity> categoryEntities) {
        List<CategoryEntity> collect = categoryEntities.stream().filter(
                entity -> {
                    return entity.getParentCid().equals(categoryEntity.getCatId());
                }
        ).map(entity -> {
            entity.setChildren(getCateGoryEntityChirds(entity, categoryEntities));
            return entity;
        }).sorted((entity1, entity2) -> {
                    return (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort());
                }
        ).collect(Collectors.toList());
        return collect;
    }

}